package com.ssy.lingxi.common.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * @description 签名相关工具类
 * @author jw.chen
 * @version 2.0.0
 * @date 2021/10/13
 */
public class SignUtils {

    /**
     * 签名的时候不携带的参数
     */
    private static final List<String> NO_SIGN_PARAMS = Arrays.asList("sign", "key");

    /**
     * 签名类型.
     */
    public static class SignType {
        /**
         * The constant HMAC_SHA256.
         */
        public static final String HMAC_SHA256 = "HMAC-SHA256";
        /**
         * The constant MD5.
         */
        public static final String MD5 = "MD5";
    }

    /**
     * 判断签名是否正确
     * @param obj           参数信息
     * @param signType      签名类型，如果为空，则默认为MD5
     * @param signKey       签名Key
     * @param ignoredParams 签名时需要忽略的特殊参数
     * @return 签名是否正确
     */
    public static boolean isSignValid(Object obj, String signType, String signKey, String[] ignoredParams) throws Exception {
        JSONObject jsonObject = JSONUtil.parseObj(obj);
        String sign = jsonObject.getStr("sign");
        if (!StringUtils.hasLength(sign)) {
            return false;
        }
        return createSign(obj, signType, signKey, ignoredParams).equals(sign);
    }

    /**
     * 创建签名
     * @param obj           参数信息
     * @param signType      签名类型，如果为空，则默认为MD5
     * @param signKey       签名Key
     * @param ignoredParams 签名时需要忽略的特殊参数
     * @return 签名字符串
     */
    public static String createSign(Object obj, String signType, String signKey, String[] ignoredParams) throws Exception {
        Map<String, Object> map = BeanUtil.beanToMap(obj);
        StringBuilder toSign = new StringBuilder();
        for (Object key : new TreeMap<>(map).keySet()) {
            String value = String.valueOf(map.get(key));
            boolean shouldSign = false;

            if (StringUtils.hasLength(value) && Arrays.asList(ignoredParams).contains(key)
                    && !NO_SIGN_PARAMS.contains(key)) {
                shouldSign = true;
            }

            if (shouldSign) {
                toSign.append(key).append("=").append(value).append("&");
            }
        }

        toSign.append("key=").append(signKey);
        if (SignType.HMAC_SHA256.equals(signType)) {
            return HMACSHA256(toSign.toString(), signKey);
        } else {
            return MD5(toSign.toString());
        }
    }

    /**
     * 生成 MD5
     * @param data 待处理数据
     * @return 加密结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
        }
        return sb.toString().toUpperCase();
    }
}
